package com.google.code.lf.commons.rules.jsr94.rulesource;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.rmi.RemoteException;
import java.util.Map;

import javax.rules.RuleServiceProvider;
import javax.rules.admin.LocalRuleExecutionSetProvider;
import javax.rules.admin.RuleAdministrator;
import javax.rules.admin.RuleExecutionSet;
import javax.rules.admin.RuleExecutionSetCreateException;
import javax.rules.admin.RuleExecutionSetRegisterException;

import org.drools.jsr94.rules.Constants;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springmodules.jsr94.rulesource.AbstractRuleSource;
import org.springmodules.jsr94.rulesource.DefaultRuleSource;
import org.springmodules.jsr94.rulesource.RuleSource;

/**
 * This class allows a session to be created from rules loaded from a given Resource.
 * <br/>
 * <br/>Adaptation basée sur l'implémentation standard proposée par le springmodule {@link DefaultRuleSource}.
 * <br/>Cette RuleSource gère les .dslr et le .dsl associé
 * 
 * @see org.springmodules.jsr94.rulesource.DefaultRuleSource
 * 
 * @author gael.lorent-fonfrede
 */
@SuppressWarnings("unchecked")
public class DslrRuleSource extends AbstractRuleSource {
	/** La source (fichier .dslr). */
	private Resource source;

	/** La bindUri (nom unique de la {@link RuleSource}). */
	private String bindUri;

	/**
	 * Local Rule execution set provider properties -- passed to the getLocalRuleExecutionSetProvider method.
	 * <br/>This field can be <code>null</code>.
	 * <br/>
	 * <br/>Il s'agit d'une {@link Map} définissant éventuellement des paramètres supplémentaires au {@link RuleServiceProvider}.
	 * <br/>Dans le cas de l'api <a href="http://www.jboss.org/drools/">drools</a>, cette map est en général <code>null</code>. Voir <a href="https://springmodules.dev.java.net/docs/reference/0.8/html/jsr94.html">documentation du springmodule</a>.
	 * 
	 * @see javax.rules.admin.RuleAdministrator#getLocalRuleExecutionSetProvider(java.util.Map)
	 */
	private Map providerProperties;

	/**
	 * Local ruleset properties -- passed to the createRuleExecutionSet method.
	 * <br/>This field can be <code>null</code>.
	 * <br/>
	 * <br/>Il s'agit d'une {@link Map} définissant éventuellement des paramètres supplémentaires au {@link RuleExecutionSet}.
	 * <br/>Dans le cas de l'api <a href="http://www.jboss.org/drools/">drools</a>, cette map est en général <code>null</code>. Voir <a href="https://springmodules.dev.java.net/docs/reference/0.8/html/jsr94.html">documentation du springmodule</a>.
	 * <br/>... sauf dans le cas de cette ruleSource, qui utilise cette map pour enregistrer les .dslr et .dsl (cf. méthode {@link #registerRuleExecutionSets()}).
	 * 
	 * @see javax.rules.admin.LocalRuleExecutionSetProvider#createRuleExecutionSet(java.io.InputStream, java.util.Map)
	 */
	private Map rulesetProperties;

	/**
	 * Rule execution set registration properties -- passed to the registerRuleExecutionSet method.
	 * <br/>This field can be <code>null</code>.
	 * <br/>
	 * <br/>Il s'agit d'une {@link Map} définissant éventuellement des paramètres supplémentaires à l'enregistrement du {@link RuleExecutionSet} auprès du {@link RuleAdministrator}.
	 * <br/>Dans le cas de l'api <a href="http://www.jboss.org/drools/">drools</a>, cette map est en général <code>null</code>. Voir <a href="https://springmodules.dev.java.net/docs/reference/0.8/html/jsr94.html">documentation du springmodule</a>.
	 * 
	 * @see javax.rules.admin.RuleAdministrator#registerRuleExecutionSet(java.lang.String, javax.rules.admin.RuleExecutionSet, java.util.Map)
	 */
	private Map registrationProperties;

	/**
	 * Enregistrement des {@link RuleExecutionSet} auprès du {@link RuleServiceProvider}.
	 * <br/>
	 * <br/>Adaptation de la méthode de la DefaultRuleSource : utilisation de la méthode {@link LocalRuleExecutionSetProvider#createRuleExecutionSet(Reader, Map) car elle gère les .dslr et .dsl si ceux-ci sont définis dans les {@link #rulesetProperties} (voir dans rulesApplicationConfig.xml)
	 * 
	 * @see org.springmodules.jsr94.support.AbstractRuleSource#registerRuleExecutionSets()
	 */
	@Override
	protected void registerRuleExecutionSets() throws RuleExecutionSetCreateException, RemoteException, IOException, RuleExecutionSetRegisterException {
		// Récupérer la ressource du ruleset
		final Reader sourceReader = new InputStreamReader(source.getInputStream());

		// Convertir la ressource du dsl en reader (nécessaire pour la méthode org.drools.jsr94.rules.admin.LocalRuleExecutionSetProviderImpl.createRuleExecutionSet(Reader, Map) pour que la compilation du package se passe correctement
		Reader dslReader = null;
		boolean isDslResourceValid = true;
		Resource dslResource = null;
		if ((rulesetProperties != null) && rulesetProperties.containsKey(Constants.RES_DSL)) {
			dslResource = new ClassPathResource((String) rulesetProperties.get(Constants.RES_DSL));
			if ((dslResource != null) && dslResource.exists()) {
				dslReader = new InputStreamReader(dslResource.getInputStream());
				if (dslReader != null) {
					rulesetProperties.put(Constants.RES_DSL, dslReader);
				} else {
					isDslResourceValid = false;
				}
			} else {
				isDslResourceValid = false;
			}
		} else {
			isDslResourceValid = false;
		}
		if (!isDslResourceValid || (dslReader == null)) {
			throw new IllegalArgumentException(new StringBuilder("Must set valid dslResource (.dsl file path) as value for ").append(Constants.RES_DSL).append(" entry in rulesetProperties map property for ").append(getClass().getName()).append(", specified one ").append(dslResource).append(" cannot be found").toString());
		}

		// Génération du ruleExecutionSet (i.e. compilation sous forme de package + properties)
		final RuleExecutionSet ruleExecutionSet = ruleAdministrator.getLocalRuleExecutionSetProvider(providerProperties).createRuleExecutionSet(sourceReader, rulesetProperties);

		// Définition éventuelle de la bindUri si elle n'est pas fournie
		if (bindUri == null) {
			logger.info(new StringBuilder("Using RuleExecutionSet().getName() [").append(ruleExecutionSet.getName()).append("] as bindUri"));
			bindUri = ruleExecutionSet.getName();
		}

		// Enregistrement du ruleExecutionSet auprès du ruleAdministrator (qui peut ensuite être appelé par le ruleRuntime via le ruleServiceProvider)
		ruleAdministrator.registerRuleExecutionSet(bindUri, ruleExecutionSet, registrationProperties);
	}

	/* (non-Javadoc)
	 * @see org.springmodules.jsr94.rulesource.AbstractRuleSource#initRuleSource()
	 */
	@Override
	protected void initRuleSource() throws Exception {
		// Validation de la ressource configurée
		if (source == null) {
			throw new IllegalArgumentException(new StringBuilder("Must set source (.dslr file path) on ").append(getClass().getName()).append(".").toString());
		}
		if ((source != null) && (!source.exists() || !source.isReadable())) {
			throw new IllegalArgumentException(new StringBuilder("Must set valid source (.dslr file path) on ").append(getClass().getName()).append(", specified one ").append(source).append(" cannot be found or read.").toString());
		}
	}

	/**
	 * @param bindUri The {@link #bindUri} to set.
	 */
	public final void setBindUri(final String bindUri) {
		this.bindUri = bindUri;
	}

	/**
	 * @param source The {@link #source} to set.
	 */
	public final void setSource(final Resource source) {
		this.source = source;
	}

	/**
	 * @param providerProperties The {@link #providerProperties} to set.
	 */
	public final void setProviderProperties(final Map providerProperties) {
		this.providerProperties = providerProperties;
	}

	/**
	 * @param registrationProperties The {@link #registrationProperties} to set.
	 */
	public final void setRegistrationProperties(final Map registrationProperties) {
		this.registrationProperties = registrationProperties;
	}

	/**
	 * @param rulesetProperties The {@link #rulesetProperties} to set.
	 */
	public final void setRulesetProperties(final Map rulesetProperties) {
		this.rulesetProperties = rulesetProperties;
	}
}
